package edu.hawaii.duedates.userlibraryinfo;

import java.net.UnknownHostException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import com.meterware.httpunit.GetMethodWebRequest;
import com.meterware.httpunit.HttpUnitOptions;
import com.meterware.httpunit.WebConversation;
import com.meterware.httpunit.WebForm;
import com.meterware.httpunit.WebLink;
import com.meterware.httpunit.WebRequest;
import com.meterware.httpunit.WebResponse;
import com.meterware.httpunit.WebTable;
import edu.hawaii.duedates.book.Book;
import edu.hawaii.duedates.library.LibraryType;
import edu.hawaii.duedates.user.User;

/**
 * The UserLibraryInfo Class implements the IUserLibraryInfo interface.  A user library info object
 * is comprised of a library, a user, and the validity of the request for information of this 
 * pair.  A UserLibraryInfo object has the behavior of requesting from the library the checked out
 * items for the user.
 * 
 * @author Ronn Reeves and Robin E. Raqueno
 */
public class UserLibraryInfo implements IUserLibraryInfo {
  
  /**  The collection of borrowed books.  */
  private List<Book> books;
  
  /**  The collection of libraries.  */
  private List<LibraryType> libraries;
  
  /**  The collection of users.  */
  private List<User> users;

  /**  The validity of the request.  */
  private List<Boolean> validities;

  /**  The count of books per library.  */
  private List<Integer> counts;
  
  /** Active internet connection flag. */
  private boolean connectedToInternet = Boolean.TRUE;
  
  
  /**
   * Creates an empty collection of user library pairs.
   */
  public UserLibraryInfo() {
    this.users = new ArrayList<User>();
    this.libraries = new ArrayList<LibraryType>();
    this.books = new ArrayList<Book>();
    this.validities = new ArrayList<Boolean>();
    this.counts = new ArrayList<Integer>();
  }

  
  /**
   * Add a user library pair to the UserLibraryInfo collection.
   * 
   * @param user  The user for whom the info is being requested.
   * @param library  The library from which the info is being to requested.
   */
  public void add(User user, LibraryType library) {
    this.users.add(user);
    this.libraries.add(library);
    this.validities.add(Boolean.FALSE);
  }


  /**
   * Sets the collection of books for this user library info request.
   * 
   * @param  books  The collection of books.
   */
  public void setBooks(List<Book> books) {
    this.books = books;
  }
  /**
   * Returns the collection of books for this user library info request.
   *
   * @return List&lt;Book&gt;  The collection of books.
   */
  public List<Book> getBooks() {
    return this.books;
  }


  /**
   * Sets this collection of libraries from which the info is to be requested.
   * 
   * @param  libraries  The library collection.
   */
  public void setLibraries(List<LibraryType> libraries) {
    this.libraries = libraries;
  }
  /**
   * Returns this collection of library objects.
   *
   * @return List&lt;LibraryType&gt;  The library collection.
   */
  public List<LibraryType> getLibraries() {
    return this.libraries;
  }


  /**
   * Sets this collection of users for which the info is to be requested.
   * 
   * @param  users  The user collection.
   */
  public void setUsers(List<User> users) {
    this.users = users;
  }
  /**
   * Returns this collection of user objects.
   *
   * @return List&lt;User&gt;  The user collection.
   */
  public List<User> getUsers() {
    return this.users;
  }


  /**
   * Sets the validity of this user library collection objects.
   * 
   * @param  validities  The validity status of each user library collection pair.
   */
  public void setValidities(List<Boolean> validities) {
    this.validities = validities;
  }
  /**
   * Returns the validity of this user library colletion pair.  Returns true if the request is 
   * successful; false, otherwise.
   *
   * @return List&lt;Boolean&gt;  The validities of each user library pair.
   */
  public List<Boolean> getValidities() {
    return this.validities;
  }

  
  /**
   * Sorts the books by the specified option.
   * 
   * @param option The sort option.
   * 
   * @return List&lt;Book&gt; The sorted list of books based on the sorting option.
   */
  private List<Book> bookSort(String option) {
    List<Book> sortedBooks = new ArrayList<Book>();
    
    // Copy books in to sortedBooks array.
    for (Book book : books) {
      sortedBooks.add(book);
    }
    
    // If sortedBooks has one or fewer books, the list is sorted.
    if (sortedBooks.size() <= 1) {
      return sortedBooks;
    }

    // Otherwise, the books need to be sorted.
    for (int outerIndex = 1; outerIndex < sortedBooks.size(); outerIndex++) {
       
      Book val = sortedBooks.get(outerIndex);
      int innerIndex;
      for (innerIndex = outerIndex - 1; innerIndex > -1; innerIndex--) {
        Book temp = sortedBooks.get(innerIndex);
        if ("library".equals(option) && temp.compareByLibrary(val) <= 0) {
          break;
        }
        else if ("duedate".equals(option) && temp.compareByDate(val) <= 0) {
          break;
        }
        sortedBooks.set(innerIndex + 1, temp);
      }
      sortedBooks.set(innerIndex + 1, val);
    }
    return sortedBooks;
  }

  
  /**
   * Returns the current page response of this web conversation.
   * 
   * @param library The library the request is for.
   * @param conversation  This current web conversation.
   * @param response  The response information of this web conservation.
   * 
   * @return WebResponse  The current page response of the WebConversation.
   */
  private WebResponse checkSummaryPage(LibraryType library, WebConversation conversation,
    WebResponse response) {
    try {
      for (WebLink weblink : response.getLinks()) {
        if (weblink.getText().equals(library.getSummary())) {
          weblink.click();
        }
      }
      return conversation.getCurrentPage();
    }
    
    catch (Exception e) {
      System.out.print(e);
      return conversation.getCurrentPage();
    }
  }

  
  /**
   * Calculates the difference between a today and this book's due date.
   * Returns 0 if the book is due today or overdue.
   * 
   * @param end The due date of the book.
   * @return difference The difference of days from today and the due date.
   */
  private long dateDiff(Date end) {
    Date today = new Date();

    // If the book is overdue return 0.
    if (end.before(today)) {
      return 0;
    }
    
    // Otherwise return the difference from today and the due date.
    long dif = (end.getTime() - today.getTime());
    return (dif / (1000L * 60L * 60L * 24L));
  }


  /**
   * Displays the checked out items information for each user and library.
   * 
   * @param sortOption The sort option (by library or due date).
   * @param withinDays The number of days until being due for the books to be displayed.
   */
  public void display(String sortOption, int withinDays) {
    if (connectedToInternet) {
      List<Book> sortedBooks = this.bookSort(sortOption);
    
      // Report the number of books checked out.
      System.out.println();
      System.out.println("LIBRARY BOOKS REPORT");
      System.out.println("==================================================");
      System.out.println(books.size() + " Book(s) Checked Out");
      System.out.println();

      // If there are no books, return to caller.
      if (books.isEmpty()) {
        return;
      }

      // Report the number of books checked out per library.
      for (int index = 0; index < libraries.size(); index++) {
        System.out.println(counts.get(index) + " book(s) from the " 
          + libraries.get(index).getName() + " for User Account " + users.get(index).getUserID());
      }

      // Otherwise, report the books based on the sorting and within options.
      System.out.println();
      System.out.println();
      String libraryLine = "---------------------------------------- ";
      String dateLine = "------------ ";
      String titleLine = 
        "---------------------------------------------------------------------------";

      if ("library".equals(sortOption)) {
        System.out.printf("%-40s %-12s %s\n", "Library", "Due Date", "Book Title");
        System.out.println(libraryLine + dateLine + titleLine);
      }

      else {
        System.out.printf("%-12s %-40s %s\n", "Due Date", "Library", "Book Title");
        System.out.println(dateLine + libraryLine + titleLine);
      }
    
      try {
        SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yy", Locale.US);
        int count = 0;

        for (Book book : sortedBooks) {
          String s1 = book.getDueDate();
          Date bookDate = dateFormat.parse(s1);
          long dateDiff = this.dateDiff(bookDate);

          if ((dateDiff <= withinDays) || (withinDays == -1)) {
            if ("library".equals(sortOption)) {
              System.out.printf("%-40s %-12s %s\n", 
                book.getLibrary(), book.getDueDate(), book.getTitle());
            }

            else {
              System.out.printf("%-12s %-40s %s\n",
                book.getDueDate(), book.getLibrary(), book.getTitle());
            }
            count++;
          }
        }

        if (withinDays > -1) {
          System.out.println();
          System.out.println(count + " book(s) due within " + withinDays + " days.");
        }
      }

      catch (ParseException e) {
        e.printStackTrace();
      }
    }
  }
  
  
  /**
   * Logs this user into this library's web site.
   * 
   * @param library The library the request is for.
   * @param user The user the request is for.
   * @param index The index for the library and user in this UserLibraryInfo object.
   * 
   * @return conversation The results page of the library's web site after logging in.
   */
  
  private WebConversation loginToLibrary(LibraryType library, User user, int index) {
    HttpUnitOptions.setExceptionsThrownOnScriptError(false);
    
    WebConversation conversation = new WebConversation();

    try {
      WebRequest request = new GetMethodWebRequest(library.getSiteUrl());
      WebResponse response = conversation.getResponse(request);

      WebLink link = null;
      if (!(library.getLink().equals(""))) {
        link = response.getLinkWith(library.getLink());
        link.click();
        response = conversation.getCurrentPage();
      }

      WebForm form = null;
      if ((library.getFormName().equals(""))) {
        for (WebForm webForm : response.getForms()) {
          if (webForm.hasParameterNamed(library.getUserFieldName())) {
            form = webForm;
          }
        }
      }
      else {
        form = response.getFormWithName(library.getFormName());
      }
      
      if ((form == null)) {
        this.validities.set(index, Boolean.FALSE);
      }
      else {
        form.setParameter(library.getUserFieldName(), user.getUserID());
        form.setParameter(library.getPasswordFieldName(), user.getPassword());
        form.submit();
        response = conversation.getCurrentPage();
      }
      
      if ((!(library.getErrorMessage().equals(""))) 
        && (response.getText().contains(library.getErrorMessage()))) {
        this.validities.set(index, Boolean.FALSE);
        conversation = null;
      }
    }

    catch (UnknownHostException e) {
      System.out.println();
      System.out.println(">>> ERROR CONNECTING TO INTERNET.  "
        + "Please connect to the internet or check your internet connection.");
      connectedToInternet = Boolean.FALSE;
      conversation = null;
    }

    catch (Exception e) {
      this.validities.set(index, Boolean.FALSE);
      System.out.println(e);
      conversation = null;
    }
    return conversation;
  }
  
  
  /**
   * Returns true if this web response contained a table with checked out items.  Otherwise,
   * return false if their are not currently checked out items.
   * 
   * @param library The library the request is for.
   * @param response  The current web page for the current web conversation
   * @param idx The index of the library in this UserLibraryInfo object.
   * 
   * @return Boolean  True if the table had checked out items; otherwise, false. 
   */
  private Boolean processBooks(LibraryType library, WebResponse response, int idx) {
    boolean isProcessed = false;
    counts.add(0);
    try {
      for (WebTable table : response.getTables()) {
        boolean isBookTable = false;
        if (table.getColumnCount() > 1) {
          int titleCol = 0;
          int dateCol = 0;
          for (int col = 0; col < table.getColumnCount(); col++) {
            if (table.getCellAsText(0, col).equals(library.getTitleColumn())) {
              titleCol = col;
            }
            if (table.getCellAsText(0, col).equals(library.getDateColumn())) {
              dateCol = col;
            }
            if (titleCol != dateCol) {
              isBookTable = true;
            }
          }

          if (isBookTable) {
            int count = 0;
            for (int row = 1; row < table.getRowCount(); row++) {
              String title = "";
              String dueDate = "";
              for (int col = 0; col < table.getColumnCount(); col++) {
                if (col == titleCol) {
                  title = table.getCellAsText(row, col).trim();  
                }
                else if (col == dateCol) {
                  dueDate = table.getCellAsText(row, col).trim();
                }
              }
              Book book = new Book(title, dueDate, library.getName());
              books.add(book);
              count++;
            }
            isProcessed = true;
            counts.set(idx, count);
          }
          
          if (isProcessed) {
            break;
          }
        }

        else if (table.getColumnCount() == 1) {
          if ((table.getCellAsText(0, 0).contains(library.getTitleColumn())) 
           && (table.getCellAsText(0, 0).contains(library.getDateColumn()))) {
            isBookTable = true;
          }

          if (isBookTable) {
            int count = 0;
            String delimiter = "\\|";
            String[] columns = table.getCellAsText(0, 0).split(delimiter);
            String title = "";
            String dueDate = "";
            for (int index = 0; index < columns.length; index++) {
              if (columns[index].contains(" /")) {
                title = columns[index].substring(0, columns[index].lastIndexOf('/')).trim();
                ++index;
                ++index;
                dueDate = columns[index].trim();
                Book book = new Book(title, dueDate, library.getName());
                books.add(book);
                count++;
              }
            }
            isProcessed = true;
            counts.set(idx, count);
          }
          
          if (isProcessed) {
            break;
          }
        }
      }
      return isProcessed;
    }
    
    catch (Exception e) {
      this.validities.set(idx, Boolean.FALSE);
      System.out.println(e);
      return isProcessed;
    }
  }
  
  
  /**
   * Requests the info from this library for this user.
   */
  public void requestInfo() {
    books = new ArrayList<Book>();
    counts = new ArrayList<Integer>();
    for (int index = 0; index < this.libraries.size(); index++) {
      LibraryType library = this.libraries.get(index);
      User user = this.users.get(index);
      String msgNoBorrowedItems = library.getNoBorrowedItemsMessage();
      this.validities.set(index, Boolean.TRUE);

      try {
        WebConversation conversation = this.loginToLibrary(library, user, index);
      
        if (!(conversation == null)) {
          WebResponse response = conversation.getCurrentPage();
        
          if (!(library.getSummary().equals(""))) {
            response = checkSummaryPage(library, conversation, response);
          }

          boolean processedBookTable = false;
          for (WebForm form : response.getForms()) {
            if (!(processedBookTable) && (!(msgNoBorrowedItems.equals(form.getText())))) {
              processedBookTable = processBooks(library, response, index);
            }
          }
        }
      }
      
      catch (Exception e) {
        this.validities.set(index, Boolean.FALSE);
      }
    }
  }


  /**
   * Returns the library name and user id of this User Library Info request.
   * 
   * @return String  The library name and user id.
   */
  public String toString() {
    StringBuffer buf = new StringBuffer();
    for (int index = 0; index < libraries.size(); ++index) {
      buf.append(this.libraries.get(index).getName());
      buf.append(" - ");
      buf.append(this.users.get(index).getUserID());
      buf.append('\n');
    }
    return buf.toString();
  }
}